/*
 * Copyright 2011 Kim Lindhardt Madsen
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package dk.lindhardt.gwt.geie.client;

import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HTMLTable;
import dk.lindhardt.gwt.geie.client.edit.HTMLTablePresenter;
import dk.lindhardt.gwt.geie.shared.*;

import java.util.ArrayList;
import java.util.List;

/**
 * User: AnAmuser
 * Date: 11-09-11
 * <p/>
 * Adds default mouse events to the table
 */
public class TableMouseEventHandler {

   private TableLayout layout;
   private HTMLTable table;
   private HTMLTablePresenter presenter;
   private List<Position> selected;

   /**
    * Adds default mouse events to the table.
    * <br/>
    * <list>
    *    <ul>Selection is starting at mouse down and ending at mouse up. Then the presenter is called.</ul>
    *    <ul>Selected cells gets the style "geie-selected-cell"</ul>
    *    <ul>Selected cells on the border is equipped with the style "geie-selected-cell-border-[side]"</ul>
    * </list>
    * @param layout the underlying layout
    * @param table the table to add events to
    * @param presenter the presenter handling selection of cells.
    */
   public TableMouseEventHandler(TableLayout layout, HTMLTable table, HTMLTablePresenter presenter) {
      this.layout = layout;
      this.table = table;
      this.presenter = presenter;
      selected = new ArrayList<Position>();
      addMouseEvents();
   }

   private void addMouseEvents() {
      final boolean[] selecting = new boolean[1];
      final Position startPosition = new Position(0, 0);
      table.addDomHandler(
            new MouseDownHandler() {
               public void onMouseDown(MouseDownEvent event) {
                  event.preventDefault();
                  int code = event.getNativeButton();
                  if (code == 1) {
                     // to prevent marking the table
                     selecting[0] = true;
                     Position position = getPositionFromEvent(event);
                     if (position != null) {
                        startPosition.setRow(position.getRow());
                        startPosition.setColumn(position.getColumn());
                     }
                  }
               }
            }, MouseDownEvent.getType());
      table.addDomHandler(
            new MouseMoveHandler() {
               public void onMouseMove(MouseMoveEvent event) {
                  event.preventDefault();
                  if (selecting[0]) {
                     Position position = getPositionFromEvent(event);
                     if (position != null && position != startPosition) {
                        List<Position> selection = createSelection(position, startPosition);
                        updateMarking(selection);
                     }
                  }
               }
            }, MouseMoveEvent.getType());
      table.addDomHandler(
            new MouseUpHandler() {
               public void onMouseUp(MouseUpEvent event) {
                  if (selecting[0]) {
                     Position position = getPositionFromEvent(event);
                     if (position != null) {
                        List<Position> selection = createSelection(position, startPosition);
                        updateMarking(selection);
                        callPresenter(selected);
                        selecting[0] = false;
                     }
                  }
               }
            }, MouseUpEvent.getType());
      table.addDomHandler(
            new MouseOutHandler() {
               public void onMouseOut(MouseOutEvent event) {
                  if (selecting[0]) {
                     callPresenter(selected);
                     selecting[0] = false;
                  }
               }
            }, MouseOutEvent.getType());
   }

   private void callPresenter(List<Position> selected) {
      if (selected.size() == 1) {
         Position singlePosition = selected.get(0);
         presenter.onTableClick(singlePosition.getRow(), singlePosition.getColumn());
      } else {
         Position topLeft = selected.get(0);
         Position bottomRight = selected.get(selected.size() - 1);
         presenter.onTableRangeSelect(
               topLeft.getRow(), topLeft.getColumn(), bottomRight.getRow(), bottomRight.getColumn());
      }
   }

   /**
    * Resets selection
    */
   public void resetMarking() {
      updateMarking(new ArrayList<Position>());
   }

   private void updateMarking(List<Position> selection) {
      selected.removeAll(selection);
      for (Position position : selected) {
         Element cellElement = table.getCellFormatter().getElement(position.getRow(), position.getColumn());
         Cell cell = layout.getCell(position.getRow(), position.getColumn(), PositioningType.HTML);
         if (cell != null) {
            CellFormat cellFormat = layout.getCellFormat(cell.getCellFormatKey());
            SerializableColor color = cellFormat.getBackgroundColor();
            if (color != null) {
               Style style = cellElement.getStyle();
               style.setBackgroundColor(color.getHexValue());
            }
         }
      }

      for (int i = 0; i < selection.size(); i++) {
         Position position = selection.get(i);
         Element cellElement = table.getCellFormatter().getElement(position.getRow(), position.getColumn());
         Style style = cellElement.getStyle();
         if (i != 0) {
            style.setBackgroundColor("#d3d3d3");
         } else {
            style.setBackgroundColor("#e6e6e6");
         }
      }

      selected.clear();
      selected.addAll(selection);
   }

   /**
    * Creates a rectangle with cells between the two given positions.
    */
   private List<Position> createSelection(Position position, Position startPosition) {
      List<Cell> range = layout.getRange(startPosition, position, PositioningType.HTML);
      List<Position> positions = new ArrayList<Position>();
      for (Cell cell : range) {
         positions.add(
               layout.getColumnCorrectionBookKeeper().applyCorrection(
                     cell.getPosition(), PositioningType.HTML));
      }
      return positions;
   }

   /**
    * Returns the position of the event. Only if a cell is found for the event
    * @param event the event
    * @return the found position if any
    */
   private Position getPositionFromEvent(HasNativeEvent event) {
      Element td = findTDElement(table, event);
      if (td != null) {
         int row = TableRowElement.as(td.getParentElement()).getSectionRowIndex();
         int col = TableCellElement.as(td).getCellIndex();
         Position position = new Position(row, col);
         String rowspan = td.getAttribute("rowspan");
         if (rowspan != null && rowspan.length() > 0) {
            position.setSpanRow(Integer.parseInt(rowspan));
         }
         String colspan = td.getAttribute("colspan");
         if (colspan != null && colspan.length() > 0) {
            position.setSpanColumn(Integer.parseInt(colspan));
         }

         return position;
      }
      return null;
   }

   /**
    * Finds a cell element from an event. If the event was outside the table this method returns null.
    * @param table the table
    * @param event the event
    * @return the cell if found
    */
   private static Element findTDElement(HTMLTable table, HasNativeEvent event) {
      Element td = DOM.eventGetTarget(Event.as(event.getNativeEvent()));
      for (; td != null; td = DOM.getParent(td)) {
         if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) {
            Element tr = DOM.getParent(td);
            Element body = DOM.getParent(tr);
            Element tableElement = DOM.getParent(body);
            if (tableElement == table.getElement()) {
               return td;
            }
         }
      }
      return null;
   }
}
